/*
 * Store.cpp
 *
 *  Created on: Jun 22, 2013
 *      Author: amatzlia
 */

#include "Store.h"
#include "Order.h"
#include "mtm_ex4.h"
#include <cassert>

Store::Store() :
		inventory(), customers(), orderCustomerMap(), revenue(0),
		albumsPurchased(0) {
}

Store::~Store() {
}

void Store::addAlbum(int barcode, int price, int year, int numPurchased,
		string title, string artist) {
	if (barcode <= 0) {
		throw mtm::INVALID_BARCODE();
	}
	if (price <= 0) {
		throw mtm::INVALID_PRICE();
	}
	if (year > currentYear) {
		throw mtm::INVALID_YEAR();
	}
	if (numPurchased < 0) {
		throw mtm::INVALID_NUM_PURCHASED();
	}

	Album newAlbum = Album(barcode, price, year, numPurchased, title, artist);
	bool albumAdded = inventory.insert(newAlbum).second;
	if (!albumAdded) {
		throw mtm::BARCODE_ALREADY_EXISTS();
	}
}

void Store::removeAlbum(int barcode) {
	set<Album>::iterator albumIterator = inventory.find(Album(barcode));
	if (albumIterator == inventory.end()) {
		throw mtm::ALBUM_DOESNT_EXIST();
	}

	inventory.erase(albumIterator);
}

void Store::addCustomer(int id, string name) {
	bool customerAdded = customers.insert(Customer(id, name)).second;
	if (!customerAdded) {
		throw mtm::CUSTOMER_ALREADY_EXISTS();
	}
}

void Store::createNewOrder(int id, int ordNum) {
	OrderCustomerPair newCustomerOrder(ordNum, id);
	set<Customer>::iterator customerIterator = customers.find(Customer(id));
	if (customerIterator == customers.end()) {
		throw mtm::CUSTOMER_DOESNT_EXIST();
	}
	Customer customer = *customerIterator;
	customer.createNewOrder(ordNum);
	bool added = orderCustomerMap.insert(newCustomerOrder).second;
	if (!added) {
		throw mtm::ORDER_ALREADY_EXISTS();
	}
	customers.erase(customerIterator);
	customers.insert(customer);
}

void Store::addToOrder(int ordNum, int barcode) {
	Order order(ordNum);
	set<Album>::iterator albumIterator = getAlbumByBarcode(barcode);
	set<Customer>::iterator customerIterator = getOrderOwner(order);
	Customer customer = *customerIterator;
	customer.addToOpenOrder(ordNum, *albumIterator);
	customers.erase(customerIterator);
	customers.insert(customer);
}

void Store::removeFromOrder(int ordNum, int barcode) {
	Order order(ordNum);
	Album albumToRemove(barcode);
	set<Customer>::iterator customerIterator = getOrderOwner(order);
	Customer customer = *customerIterator;
	customer.removeFromOpenOrder(ordNum, albumToRemove);
	customers.erase(customerIterator);
	customers.insert(customer);
}

void Store::payOrder(int ordNum) {
	set<Customer>::iterator customerIterator = getOrderOwner(Order(ordNum));
	Customer buyer = *customerIterator;
	List<Album> paidAlbums = buyer.payForOpenOrder(ordNum);

	verifyAlbumsExist(paidAlbums);

	for (const Album& albumToBuy : paidAlbums) {
		updateAfterAlbumPurchased(albumToBuy);
	}

	customers.erase(customerIterator);
	customers.insert(buyer);
}

string Store::getRecommendations(int numOfRecommendations) const {
	if (numOfRecommendations <= 0) {
		throw mtm::INVALID_NUMBER_OF_RECOMMENDATIONS();
	}
	if (inventory.size() < static_cast<unsigned int>(numOfRecommendations)) {
		throw mtm::NOT_ENOUGH_RECOMMENDATIONS();
	}

	List<Album> sortedAlbums;
	for (const Album& album : inventory) {
		sortedAlbums.insert(album);
	}

	sortedAlbums.sort(Album::SortByNumPurchasedThenBarcode());

	string outputString = mtm::getRecommendationsHeaderString(
			numOfRecommendations);

	for (const Album& album : sortedAlbums) {
		outputString = outputString + album.getFullString();
		if (--numOfRecommendations == 0) {
			break;
		}
	}

	return outputString;
}

string Store::getInventoryReport() const {
	const int inventorySize = inventory.size();
	if (inventorySize == 0) {
		throw mtm::EMPTY_INVENTORY();
	}

	string output = mtm::getAlbumsHeaderString(inventorySize);
	for (const Album& album : inventory) {
		output += album.getFullString();
	}

	return output;
}

string Store::getCustomersReport() const {
	const int numberOfCustomers = customers.size();
	if (numberOfCustomers == 0) {
		throw mtm::NO_CUSTOMERS();
	}

	string output = mtm::getCustomersHeaderString(numberOfCustomers);
	for (const Customer& customer : customers) {
		output += customer.getDescriptionString();
	}

	return output;
}

string Store::getCustomerReport(int id) const {
	set<Customer>::iterator customerIterator = customers.find(Customer(id));
	if (customerIterator == customers.end()) {
		throw mtm::CUSTOMER_DOESNT_EXIST();
	}

	const Customer& customer = *customerIterator;
	return customer.getReportString();
}

string Store::getRevenueReport() const {
	string outputString = mtm::getRevenueHeaderString(customers.size(),
			albumsPurchased, revenue);

	for (const Customer& customer : customers) {
		outputString += customer.getRevenueString();
	}

	return outputString;
}

/* ############################ Private Methods ############################ */

set<Album>::iterator Store::getAlbumByBarcode(int barcode) const {
	set<Album>::iterator albumIterator = inventory.find(Album(barcode));
	if (albumIterator == inventory.end()) {
		throw mtm::ALBUM_DOESNT_EXIST();
	}
	return albumIterator;
}

set<Customer>::iterator Store::getOrderOwner(const Order& order) const {
	auto pairIterator = orderCustomerMap.find(OrderCustomerPair(order.getId()));
	if (pairIterator == orderCustomerMap.end()) {
		throw mtm::ORDER_DOESNT_EXIST();
	}
	set<Customer>::iterator customerIterator = customers.find(
			Customer(pairIterator->customerId));
	assert(customerIterator != customers.end());
	return customerIterator;
}

void Store::verifyAlbumsExist(const List<Album>& albums) const {
	for (const Album& album : albums) {
		if (inventory.find(album) == inventory.end()) {
			throw mtm::ALBUM_MISSING();
		}
	}
}

void Store::updateAfterAlbumPurchased(const Album& album) {
	set<Album>::iterator albumIterator = inventory.find(album);
	assert(albumIterator != inventory.end());
	Album updatedAlbum = *albumIterator;
	revenue += updatedAlbum.purchase();
	albumsPurchased++;
	inventory.erase(albumIterator);
	inventory.insert(updatedAlbum);
}

Store::OrderCustomerPair::OrderCustomerPair(int orderId, int customerId) :
		orderId(orderId), customerId(customerId) {
	if (customerId <= 0) {
		throw mtm::INVALID_ID();
	}
	if (orderId <= 0) {
		throw mtm::INVALID_ORDER_NUMBER();
	}
}

Store::OrderCustomerPair::OrderCustomerPair(int orderId) :
		orderId(orderId), customerId(0) {
	if (orderId <= 0) {
		throw mtm::INVALID_ORDER_NUMBER();
	}
}

/* ############################ Friend functions ########################### */

bool operator<(const Store::OrderCustomerPair& pair1,
		const Store::OrderCustomerPair& pair2) {
	return pair1.orderId < pair2.orderId;
}
